#include "preHeader.h"
#include "Unit.h"
#include "Map/MapInstance.h"
#include "Spell/SpellMgr.h"

Unit::Unit(OBJECT_TYPE objType)
: LocatableObject(objType)
, m_pProto(NULL)
, m_attribute(this)
, m_hpMaxReachable(0)
, m_mpMaxReachable(0)
, m_isRecoveryHPDisable(false)
, m_moveMode(MoveByWalk)
, m_pPriorZone(NULL)
, m_isDead(false)
, m_pTarget(NULL)
, m_lastCombatTime(0)
, m_isInCombat(false)
, m_priorGuid(ObjGUID_NULL)
, m_dozeDist(0)
, m_dullDist(0)
, m_keepDist(.0f)
, m_iMovePathCount(0)
, m_isMoving(false)
, m_isPlanMoving(false)
, m_isPriorMove(false)
, m_isTurning(false)
, m_fgSpell(NULL)
, m_spellBuffUniqueKey(0)
{
}

Unit::~Unit()
{
	DestructAllSpells();
	DestructAllSpellBuffInfos();
}

bool Unit::Init(uint32 entry)
{
	m_pProto = GetDBEntry<CharPrototype>(entry);
	if (m_pProto == NULL) {
		return false;
	}

	SetF32Value(UNIT_FLOAT_MOVE_SPEED, m_pProto->speedWalk);
	SetF32Value(UNIT_FLOAT_TURN_SPEED, m_pProto->speedTurn);

	SetTeamSide(ArenaTeamSide::teamInvalid);

	if (!SubInit()) {
		return false;
	}

	ReloadAttributes();
	return true;
}

bool Unit::SubInit()
{
	return true;
}

void Unit::Update(uint64 diffTime)
{
	if (m_pTarget != NULL && !m_pTarget->IsAlive()) {
		SetTarget(NULL);
	}

	int64 availTime = diffTime;
	availTime = UpdateTurn(availTime);
	availTime = UpdateMove(availTime);

	RecoveryHPValue(diffTime);
	RecoveryMPValue(diffTime);

	SubUpdate(diffTime);

	CleanSpells();
	CleanSpellBuffInfos();

	LocatableObject::Update(diffTime);
}

void Unit::SubUpdate(uint64 diffTime)
{
}

void Unit::OnDelete()
{
	InterruptAllReferAuraObjects();
	LocatableObject::OnDelete();
}

const std::string& Unit::GetName() const
{
	return I18N_TEXT(m_pProto->charTypeId, STRING_TEXT_TYPE::CHAR_NAME);
}

void Unit::BuildCreatePacketForPlayer(INetPacket& pck, Player* pPlayer)
{
	LocatableObject::BuildCreatePacketForPlayer(pck, pPlayer);
	pck << m_pProto->charTypeId << (s8)m_moveMode
		<< m_isDead << m_isInCombat;
	pck << m_isMoving;
	if (m_isMoving) {
		BuildCreateUpdateMoveBlock(pck);
	}
	pck << m_isTurning;
	if (m_isTurning) {
		BuildCreateUpdateTurnBlock(pck);
	}
}

float Unit::GetCombatBestDist(Unit* pEnemy) const
{
	return m_pProto->boundRadius + m_pProto->combatBestDist +
		pEnemy->m_pProto->boundRadius;
}

Player* Unit::GetPlayerOwner()
{
	if (IsType(TYPE_PLAYER)) {
		return static_cast<Player*>(this);
	}
	return NULL;
}

void Unit::ReloadAttributes()
{
	ReloadPartAttributes((u32)ATTRPARTTYPE::ALL);
}

void Unit::ReloadPartAttributes(uint32 flags)
{
	m_attribute.Reload(flags);
	ReloadHPMPValue();
}

void Unit::ReloadHPMPValue()
{
	SetS64Value(UNIT64_FIELD_HP_MAX,
		(s64)m_attribute.GetAttr(ATTRTYPE::HIT_POINT));
	SetS64Value(UNIT64_FIELD_MP_MAX,
		(s64)m_attribute.GetAttr(ATTRTYPE::MAGIC_POINT));
	SetS64Value(UNIT64_FIELD_HP, std::min(GetS64Value(UNIT64_FIELD_HP_MAX),
		std::max(GetS64Value(UNIT64_FIELD_HP), m_hpMaxReachable)));
	SetS64Value(UNIT64_FIELD_MP, std::min(GetS64Value(UNIT64_FIELD_MP_MAX),
		std::max(GetS64Value(UNIT64_FIELD_MP), m_mpMaxReachable)));
}

void Unit::FastRevive()
{
	m_hpMaxReachable = INT64_MAX;
	m_mpMaxReachable = INT64_MAX;
	SetS64Value(UNIT64_FIELD_HP, GetS64Value(UNIT64_FIELD_HP_MAX));
	SetS64Value(UNIT64_FIELD_MP, GetS64Value(UNIT64_FIELD_MP_MAX));
	if (IsDead()) {
		OnRevive();
	}
}

void Unit::OnRevive()
{
	m_isDead = false;

	NetPacket pack(SMSG_UNIT_REVIVE);
	pack << GetGuid();
	SendMessageToSet(pack);
}

bool Unit::RecoveryHPValue(uint64 diffTime)
{
	if (m_isRecoveryHPDisable) {
		return false;
	}
	if (m_hpMaxReachable >= INT64_MAX) {
		return false;
	}
	if (IsInCombat() || IsDead()) {
		return false;
	}

	auto hpRate = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_HP_RATE);
	auto hpValue = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_HP_VALUE);
	if (hpRate <= 0 && hpValue <= 0) {
		return false;
	}

	auto addValue = GetS64Value(UNIT64_FIELD_HP_MAX) * hpRate + hpValue;
	if (addValue > 0) {
		AddHPValue(uint64(addValue * diffTime / 1000));
	}

	return true;
}

bool Unit::RecoveryMPValue(uint64 diffTime)
{
	if (m_mpMaxReachable >= INT64_MAX) {
		return false;
	}
	if (IsDead()) {
		return false;
	}

	auto mpRate = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_MP_RATE);
	auto mpValue = m_attribute.GetAttrEx(ATTREXTYPE::RECOVERY_MP_VALUE);
	if (mpRate <= 0 && mpValue <= 0) {
		return false;
	}

	auto addValue = GetS64Value(UNIT64_FIELD_MP_MAX) * mpRate + mpValue;
	if (addValue > 0) {
		AddMPValue(uint64(addValue * diffTime / 1000));
	}

	return true;
}

void Unit::ForceMotionless()
{
	CancelPlanMove();
	StopMove();
	StopTurn();
}

float Unit::GetMoveSpeed() const
{
	float speed = GetF32Value(UNIT_FLOAT_MOVE_SPEED);
	float scale = GetF32Value(UNIT_FLOAT_MOVE_SPEED_INC);
	return std::max(speed * std::max(scale + 1.f, .0f), FLT_MIN);
}

float Unit::GetTurnSpeed() const
{
	float speed = GetF32Value(UNIT_FLOAT_TURN_SPEED);
	float scale = GetF32Value(UNIT_FLOAT_TURN_SPEED_INC);
	return std::max(speed * std::max(scale + 1.f, .0f), FLT_MIN);
}

void Unit::ModMoveSpeedInc(float inc)
{
	ModF32Value(UNIT_FLOAT_MOVE_SPEED_INC, inc);
}

void Unit::ModTurnSpeedInc(float inc)
{
	ModF32Value(UNIT_FLOAT_TURN_SPEED_INC, inc);
}

void Unit::SetMoveMode(MoveMode moveMode)
{
	if (m_moveMode == moveMode) {
		return;
	}

	m_moveMode = moveMode;
	switch (moveMode) {
	case MoveByWalk:
		SetF32Value(UNIT_FLOAT_MOVE_SPEED, m_pProto->speedWalk);
		break;
	case MoveByRun:
		SetF32Value(UNIT_FLOAT_MOVE_SPEED, m_pProto->speedRun);
		break;
	default:
		assert(false && "can't reach here.");
		break;
	}

	if (IsMoving()) {
		NetPacket pack(SMSG_UNIT_CHANGE_MOVE);
		pack << GetGuid() << MoveChangeMoveMode << moveMode;
		SendMessageToSet(pack, true);
	}
}

void Unit::OnChangePriorZone(const MapZone* pOldZone)
{
}

void Unit::OnChangePosition()
{
	auto pOldZone = m_pPriorZone;
	m_pPriorZone = FilterPriorZone();
	if (m_pPriorZone != pOldZone) {
		OnChangePriorZone(pOldZone);
	}

	MoveAllReferAuraObjects();

	LocatableObject::OnChangePosition();
}

const MapZone* Unit::FilterPriorZone() const
{
	const MapZone* pPriorZone = NULL;
	for (auto& pMapZone :
		m_pMapInstance->getGameMap().GetMapZones(GetMapType())) {
		if (!IsInRange(m_position.x, pMapZone->x1, pMapZone->x2) ||
			!IsInRange(m_position.z, pMapZone->z1, pMapZone->z2)) {
			continue;
		}
		if (pPriorZone != NULL &&
			pPriorZone->priority > pMapZone->priority) {
			continue;
		}
		pPriorZone = pMapZone;
	}
	return pPriorZone;
}

uint32 Unit::GetPriorZoneFlags() const
{
	return m_pPriorZone != NULL ? m_pPriorZone->pvp_flags : 0;
}

bool Unit::IsFriend(const Unit* pTarget) const
{
	if (pTarget == this || pTarget == NULL) {
		return false;
	}
	if (!pTarget->m_pProto->charFlags.isJoinCombat) {
		return false;
	}
	if (!m_pProto->charFlags.isJoinCombat) {
		return false;
	}

	if (IsHostile(pTarget)) {
		return false;
	}

	return true;
}

bool Unit::IsHostile(const Unit* pTarget) const
{
	return false;
}

bool Unit::CanAttack(Unit* pTarget)
{
	if (IsOutOfControl()) {
		return false;
	}
	if (!IsHostile(pTarget)) {
		return false;
	}
	return true;
}

void Unit::SetTeamSide(ArenaTeamSide teamSide)
{
	SetS32Value(UNIT_FIELD_TEAM_SIDE, teamSide);
}

ArenaTeamSide Unit::GetTeamSide() const
{
	return (ArenaTeamSide)GetS32Value(UNIT_FIELD_TEAM_SIDE);
}

void Unit::AttachOverlayAuraState(AURA_STATE auraState)
{
	auto& auraInfo = m_auraInfos[auraState];
	auraInfo.auraNum += 1;
	auto auraStateFlag = 1 << auraState;
	if (!HasFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag)) {
		SetFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag);
		OnNewAuraState();
	}
}

void Unit::DetachOverlayAuraState(AURA_STATE auraState)
{
	auto& auraInfo = m_auraInfos[auraState];
	auraInfo.auraNum -= 1;
	auto auraStateFlag = 1 << auraState;
	if (auraInfo.auraNum == 0 && !auraInfo.hasSysAura) {
		RemoveFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag);
	}
}

void Unit::AddSysAuraState(AURA_STATE auraState)
{
	auto& auraInfo = m_auraInfos[auraState];
	auraInfo.hasSysAura = true;
	auto auraStateFlag = 1 << auraState;
	if (!HasFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag)) {
		SetFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag);
		OnNewAuraState();
	}
}

void Unit::RemoveSysAuraState(AURA_STATE auraState)
{
	auto& auraInfo = m_auraInfos[auraState];
	auraInfo.hasSysAura = false;
	auto auraStateFlag = 1 << auraState;
	if (auraInfo.auraNum == 0 && !auraInfo.hasSysAura) {
		RemoveFlag(UNIT_FIELD_AURA_FLAGS, auraStateFlag);
	}
}

void Unit::OnNewAuraState()
{
	if (IsOutOfControl()) {
		OnOutOfControl();
	}
	if (IsOutOfMove()) {
		OnOutOfMove();
	}
}

void Unit::OnOutOfControl()
{
	if (IsSpellCasting()) {
		auto pSpellProto = GetSpellCasting()->GetSpellProto();
		if (!pSpellProto->siInfo->spellFlags.isIgnoreOutOfControl) {
			InterruptFgSpell();
		}
	}
}

void Unit::OnOutOfMove()
{
	ForceMotionless();
}

bool Unit::IsOutOfControl() const
{
	return HasOneOfFlag(UNIT_FIELD_AURA_FLAGS,
		AURA_STATE_FLAG_STUN);
}

bool Unit::IsOutOfMove() const
{
	return HasOneOfFlag(UNIT_FIELD_AURA_FLAGS,
		AURA_STATE_FLAG_FETTER |
		AURA_STATE_FLAG_STUN);
}

void Unit::Strike(Unit* pVictim, LuaTable t)
{
	if (pVictim == NULL || pVictim->IsDead() || IsDead()) {
		return;
	}

	double hitChance = m_attribute.GetHitChance(pVictim);
	if (System::Randf(0, 1) > hitChance) {
		return;
	}

	double hurtRate = 1.;
	float hpValue = LuaFuncs(t).CallMethod<float>("CalcDamage", pVictim);

	double critiHitChance = m_attribute.GetCritiHitChance(pVictim);
	if (System::Randf(0, 1) <= critiHitChance) {
		hurtRate = m_attribute.GetCritiHitIntensity(pVictim);
	}

	pVictim->m_strikeHook = [=, &t](uint64 strikeValue) {
		auto pSpell = t.get<Spell*>("spell");
		auto sleIdx = t.get<size_t>("index");
		NetPacket strikePacket(SMSG_UNIT_STRIKE);
		strikePacket << GetGuid() << pVictim->GetGuid()
			<< pSpell->GetSpellInstGuid()
			<< pSpell->GetSpellProto()->siInfo->spellID
			<< pSpell->GetSpellLevel() << (uint16)sleIdx << strikeValue;
		SendMessageToSet(strikePacket);
	};

	uint64 finalStrikeValue = pVictim->Hurt(std::max(hpValue * hurtRate, 1.), this);
	pVictim->m_strikeHook = NULL;

	if (pVictim != this && !pVictim->IsDead()) {
		this->AddEnemy(pVictim);
		this->SetInCombat(true);
		this->m_lastCombatTime = GET_UNIX_TIME;
	}
}

uint64 Unit::Hurt(double hpValue, Unit* pHurter)
{
	if (IsDead()) {
		return 0;
	}

	m_hurtHook = [=](uint64 hurtValue) {
		m_pMapInstance->MapHookEvent_OnUnitHurted(this, pHurter, hurtValue);
		ObjectHookEvent_OnUnitHurted(pHurter, hurtValue);
		SpellBuffEvent_OnHurted(pHurter, hurtValue);
		if (pHurter != NULL) {
			pHurter->SpellBuffEvent_OnHurt(this, hurtValue);
		}
		NetPacket strikePacket(SMSG_UNIT_HURT);
		strikePacket << GetGuid()
			<< (pHurter != NULL ? pHurter->GetGuid() : ObjGUID_NULL)
			<< hurtValue;
		SendMessageToSet(strikePacket);
	};

	uint64 finalHurtValue = LoseHP(hpValue, pHurter);
	m_hurtHook = NULL;

	return finalHurtValue;
}

uint64 Unit::LoseHP(double hpValue, Unit* pHurter)
{
	if (IsDead()) {
		return 0;
	}
	if (!CanLostHP()) {
		return 0;
	}

	auto finalLoseHPValue = (u64)std::round(hpValue);
	SubHPValue(finalLoseHPValue);

	if (m_strikeHook) {
		m_strikeHook(finalLoseHPValue);
	}
	if (m_hurtHook) {
		m_hurtHook(finalLoseHPValue);
	}

	OnLoseHP(pHurter, finalLoseHPValue);

	NetPacket loseHPPacket(SMSG_UNIT_LOSE_HP);
	loseHPPacket << GetGuid()
		<< (pHurter != NULL ? pHurter->GetGuid() : ObjGUID_NULL)
		<< finalLoseHPValue;
	SendMessageToSet(loseHPPacket);

	if (GetS64Value(UNIT64_FIELD_HP) <= 0) {
		if (!CanKilled()) {
			SetS64Value(UNIT64_FIELD_HP, 1);
		}
	}
	if (GetS64Value(UNIT64_FIELD_HP) <= 0) {
		OnKilled(pHurter);
		if (GetS64Value(UNIT64_FIELD_HP) <= 0) {
			OnDead(pHurter);
		}
	}

	if (pHurter != NULL && pHurter != this && !IsDead()) {
		this->AddEnemy(pHurter);
		this->SetInCombat(true);
		this->m_lastCombatTime = GET_UNIX_TIME;
	}

	return finalLoseHPValue;
}

uint64 Unit::TreatHP(double hpValue, Unit* pTreater)
{
	auto finalTreatHPValue = (u64)std::round(hpValue);
	AddHPValue(finalTreatHPValue);

	NetPacket treatHPPacket(SMSG_UNIT_TREAT_HP);
	treatHPPacket << GetGuid()
		<< (pTreater != NULL ? pTreater->GetGuid() : ObjGUID_NULL)
		<< finalTreatHPValue;
	SendMessageToSet(treatHPPacket);

	return finalTreatHPValue;
}

void Unit::AddHPValue(uint64 hpValue)
{
	if (hpValue == 0) return;
	DBGASSERT(hpValue <= INT64_MAX);
	ModS64Value(UNIT64_FIELD_HP, hpValue);
	if (GetS64Value(UNIT64_FIELD_HP) >= GetS64Value(UNIT64_FIELD_HP_MAX)) {
		SetS64Value(UNIT64_FIELD_HP, GetS64Value(UNIT64_FIELD_HP_MAX));
		m_hpMaxReachable = INT64_MAX;
	}
}

void Unit::SubHPValue(uint64 hpValue)
{
	if (hpValue == 0) return;
	DBGASSERT(hpValue <= INT64_MAX);
	ModS64Value(UNIT64_FIELD_HP, neg(hpValue));
	m_hpMaxReachable = 0;
}

void Unit::AddMPValue(uint64 mpValue)
{
	if (mpValue == 0) return;
	DBGASSERT(mpValue <= INT64_MAX);
	ModS64Value(UNIT64_FIELD_MP, mpValue);
	if (GetS64Value(UNIT64_FIELD_MP) >= GetS64Value(UNIT64_FIELD_MP_MAX)) {
		SetS64Value(UNIT64_FIELD_MP, GetS64Value(UNIT64_FIELD_MP_MAX));
		m_mpMaxReachable = INT64_MAX;
	}
}

void Unit::SubMPValue(uint64 mpValue)
{
	if (mpValue == 0) return;
	DBGASSERT(mpValue <= INT64_MAX);
	ModS64Value(UNIT64_FIELD_HP, neg(mpValue));
	m_mpMaxReachable = 0;
}

void Unit::OnLoseHP(Unit* pHurter, uint64 hurtValue)
{
}

bool Unit::CanLostHP() const
{
	if (m_attribute.GetAttrEx(ATTREXTYPE::CANT_LOSE_HP) != 0) {
		return false;
	}
	return true;
}

bool Unit::CanKilled() const
{
	if (m_pProto->charFlags.isUndead) {
		return false;
	}
	if (m_attribute.GetAttrEx(ATTREXTYPE::CANT_DEAD) != 0) {
		return false;
	}
	return true;
}

void Unit::OnKilled(Unit* pKiller)
{
	m_pMapInstance->MapHookEvent_OnUnitKilled(this, pKiller);
	if (GetS64Value(UNIT64_FIELD_HP) > 0) {
		return;
	}
	ObjectHookEvent_OnUnitKilled(pKiller);
	if (GetS64Value(UNIT64_FIELD_HP) > 0) {
		return;
	}
	SpellBuffEvent_OnKilled(pKiller);
	if (GetS64Value(UNIT64_FIELD_HP) > 0) {
		return;
	}
	if (pKiller != NULL) {
		pKiller->SpellBuffEvent_OnKill(this);
	}
	if (GetS64Value(UNIT64_FIELD_HP) > 0) {
		return;
	}
}

void Unit::OnDead(Unit* pKiller)
{
	m_isDead = true;

	m_pMapInstance->MapHookEvent_OnUnitDead(this, pKiller);
	ObjectHookEvent_OnUnitDead(pKiller);

	NetPacket pack(SMSG_UNIT_DEAD);
	pack << GetGuid();
	if (pKiller != NULL) {
		pack << pKiller->GetGuid() << pKiller->GetName();
	}
	SendMessageToSet(pack);
}

void Unit::AddEnemy(Unit* enemy)
{
	ObjGUID enemyGuid = enemy->GetGuid();
	m_enemyList.emplace(enemyGuid, EnemyInfo{enemyGuid, 1});
	m_enemySenseList.erase(enemyGuid);
}

void Unit::AddSenseEnemy(Unit* enemy)
{
	ObjGUID enemyGuid = enemy->GetGuid();
	if (m_enemyList.count(enemyGuid) == 0) {
		m_enemySenseList.insert(enemyGuid);
	}
}

void Unit::RemoveEnemy(ObjGUID enemyGuid)
{
	m_enemyList.erase(enemyGuid);
}

bool Unit::HasEnemy(ObjGUID enemyGuid) const
{
	return m_enemyList.count(enemyGuid) != 0;
}

void Unit::ClearEnemyList()
{
	m_enemyList.clear();
}

void Unit::CleanEnemySenseList()
{
	auto IsEnemySenseAvail = [=](Unit* pEnemy) {
		if (pEnemy == NULL || pEnemy->IsDead()) {
			return false;
		}
		if (!pEnemy->HasEnemy(GetGuid())) {
			return false;
		}
		return true;
	};

	auto itr = m_enemySenseList.begin();
	while (itr != m_enemySenseList.end()) {
		auto pEnemy = m_pMapInstance->GetUnit(*itr);
		if (!IsEnemySenseAvail(pEnemy)) {
			itr = m_enemySenseList.erase(itr);
		} else {
			++itr;
		}
	}
}

void Unit::SetTarget(Unit* pTarget)
{
	if (m_pTarget == pTarget) {
		return;
	}

	m_pTarget = pTarget;
	if (m_pTarget != NULL) {
		AddEnemy(m_pTarget);
	}

	NetPacket pack(SMSG_CHANGE_TARGET);
	pack << GetGuid() << GetTargetGuid();
	SendMessageToSet(pack);
}

bool Unit::SearchEnemyInSight()
{
	if (m_pProto->senseRadius <= .0f) {
		return false;
	}

	auto TryAddEnemy4Marker = [=](AoiActor* actor) {
		auto pUnit = static_cast<Unit*>(actor);
		if (pUnit->IsDead()) {
			return false;
		}
		if (!m_pProto->charFlags.isIgnoreInvisible &&
			pUnit->HasFlag(UNIT_FIELD_AURA_FLAGS, AURA_STATE_FLAG_INVISIBLE)) {
			return false;
		}
		if (HasEnemy(pUnit->GetGuid())) {
			return false;
		}
		if (!CanAttack(pUnit)) {
			return false;
		}
		if (!HasTarget()) {
			SetTarget(pUnit);
		} else {
			AddEnemy(pUnit);
		}
		return true;
	};

	if (m_pProto->charFlags.isSensePlayer) {
		if (AoiActor::ForeachMarker(AoiActor::PLAYER, TryAddEnemy4Marker)) {
			return true;
		}
	}
	if (m_pProto->charFlags.isSenseCreature) {
		if (AoiActor::ForeachMarker(AoiActor::CREATURE, TryAddEnemy4Marker)) {
			return true;
		}
	}

	return false;
}

bool Unit::TryLockAttackObject()
{
	if (m_pTarget != NULL) {
		return true;
	}
	if (SearchEnemyInSight()) {
		return true;
	}
	return false;
}

void Unit::ResetToIdle()
{
	SetTarget(NULL);
	ClearEnemyList();
	SetInCombat(false);
	OnResetToIdle();
}

void Unit::SetInCombat(bool isInCombat)
{
	if (m_isInCombat != isInCombat) {
		m_isInCombat = isInCombat;
		OnChangeCombatStatus();
	}
}

void Unit::OnResetToIdle()
{
}

void Unit::OnChangeCombatStatus()
{
	ObjectHookEvent_OnUnitChangeCombatStatus();
}

vector3f Unit::GetEnemySlotDir(Unit* enemy)
{
	auto rst = TryFastGetEnemySlotDir(enemy);
	if (rst.second) {
		return rst.first;
	}

	auto enemyList = CleanEnemySlot(enemy);
	if (enemyList.empty()) {
		auto rst = TryFastGetEnemySlotDir(enemy);
		if (rst.second) {
			return rst.first;
		}
	}

	auto enemyCnt = enemyList.size() +
		(IS_VECTOR_CONTAIN_VALUE(enemyList, enemy) ? 0 : 1) -
		std::count(enemyList.begin(), enemyList.end(), nullptr);
	if (enemyCnt != m_enemySlotList.size() &&
		(enemyCnt > UNIT_AROUND_ENEMY_SLOT_MIN ||
		 m_enemySlotList.size() > UNIT_AROUND_ENEMY_SLOT_MIN))
	{
		enemyList.erase(std::remove(
			enemyList.begin(), enemyList.end(), nullptr), enemyList.end());
		if (!IS_VECTOR_CONTAIN_VALUE(enemyList, enemy)) {
			enemyList.push_back(enemy);
		}
		RelocateAllEnemySlot(enemyList);
	} else {
		RelocateEnemySlot(enemyList, enemy);
	}

	auto enemyGuid = enemy->GetGuid();
	auto itr = std::find_if(m_enemySlotList.begin(), m_enemySlotList.end(),
		[=](const EnemySlot& enemySlot) {
		return enemySlot.enemy == enemyGuid;
	});
	if (itr != m_enemySlotList.end()) {
		return itr->slotDir;
	}

	return {};
}

std::pair<vector3f, bool> Unit::TryFastGetEnemySlotDir(Unit* enemy)
{
	if (m_enemySlotList.empty()) {
		m_enemySlotList.push_back({enemy->GetGuid(),
			vector3f_normalize(enemy->GetPosition() - GetPosition())});
		return {m_enemySlotList.front().slotDir, true};
	}

	if (m_enemySlotList.size() == 1) {
		auto& enemySlot = m_enemySlotList.front();
		if (enemySlot.enemy == enemy->GetGuid()) {
			(enemySlot.slotDir =
				enemy->GetPosition() - GetPosition()).normalize();
			return {enemySlot.slotDir, true};
		}
	}

	return {{}, false};
}

std::vector<Unit*> Unit::CleanEnemySlot(Unit* enemy)
{
	std::vector<Unit*> enemyList(m_enemySlotList.size());
	auto IsEnemyAvail = [=](Unit* pEnemy) {
		return pEnemy != NULL && !pEnemy->IsDead() &&
			pEnemy->GetTarget() == this;
	};

	auto enemyGuid = enemy->GetGuid();
	for (size_t i = 0, n = m_enemySlotList.size(); i < n; ++i) {
		auto& enemySlot = m_enemySlotList[i];
		if (enemySlot.enemy != ObjGUID_NULL) {
			auto pEnemy = enemySlot.enemy == enemyGuid ?
				enemy : m_pMapInstance->GetUnit(enemySlot.enemy);
			if (IsEnemyAvail(pEnemy)) {
				enemyList[i] = pEnemy;
			} else {
				enemySlot.enemy = ObjGUID_NULL;
			}
		}
	}

	auto n = std::count(enemyList.begin(), enemyList.end(), nullptr);
	if (n == enemyList.size()) {
		m_enemySlotList.clear();
		return {};
	}

	if (n + 1 == enemyList.size()) {
		auto itr = std::find(enemyList.begin(), enemyList.end(), enemy);
		if (itr != enemyList.end()) {
			m_enemySlotList.clear();
			return {};
		}
	}

	return enemyList;
}

void Unit::RelocateAllEnemySlot(const std::vector<Unit*>& enemyList)
{
	m_enemySlotList.resize(
		std::min<size_t>(enemyList.size(), UNIT_AROUND_ENEMY_SLOT_MIN));

	auto firstSlotDir = vector3f_normalize(
		enemyList.front()->GetPosition() - GetPosition());
	for (size_t i = 0, n = m_enemySlotList.size(); i < n; ++i) {
		m_enemySlotList[i] = {ObjGUID_NULL, firstSlotDir};
		m_enemySlotList[i].slotDir.rotateXZBy(2 * PI * i / n);
	}

	std::vector<bool> enemyBits(enemyList.size());
	for (size_t i = 0, n = m_enemySlotList.size(); i < n; ++i) {
		auto& enemySlot = m_enemySlotList[i];
		auto& slotDir = enemySlot.slotDir;
		auto sltIndex = SIZE_MAX;
		auto sltDist = FLT_MAX;
		for (size_t i = 0, n = enemyList.size(); i < n; ++i) {
			if (enemyBits[i]) { continue; }
			auto pCurEnemy = enemyList[i];
			auto curDist = pCurEnemy->GetPosition().DistanceSq(
				GetPosition() + slotDir * pCurEnemy->GetCombatBestDist(this));
			if (sltDist > curDist) {
				sltIndex = i, sltDist = curDist;
			}
		}
		enemyBits[sltIndex] = true;
		enemySlot.enemy = enemyList[sltIndex]->GetGuid();
	}
}

void Unit::RelocateEnemySlot(const std::vector<Unit*>& enemyList, Unit* enemy)
{
	auto sltIndex = SIZE_MAX;
	auto sltDist = FLT_MAX;
	auto enemyIndex = SIZE_MAX;
	for (size_t i = 0, n = m_enemySlotList.size(); i < n; ++i) {
		auto& enemySlot = m_enemySlotList[i];
		auto& slotDir = enemySlot.slotDir;
		auto curDist = enemy->GetPosition().Distance(
			GetPosition() + slotDir * enemy->GetCombatBestDist(this));
		auto pCurEnemy = enemyList[i];
		if (pCurEnemy != NULL && pCurEnemy != enemy) {
			auto curEnemyDist = pCurEnemy->GetPosition().Distance(
				GetPosition() + slotDir * pCurEnemy->GetCombatBestDist(this));
			if (curEnemyDist < curDist + UNIT_AROUND_ENEMY_GRAB_DIST) {
				continue;
			}
		}
		if (pCurEnemy != NULL && pCurEnemy == enemy) {
			enemyIndex = i;
		}
		if (pCurEnemy != NULL) {
			if (pCurEnemy == enemy) {
				curDist -= UNIT_AROUND_ENEMY_SWITCH_DIST;
			} else {
				curDist += UNIT_AROUND_ENEMY_SWITCH_DIST;
			}
		}
		if (sltDist > curDist) {
			sltIndex = i, sltDist = curDist;
		}
	}

	if (sltIndex != enemyIndex) {
		if (enemyIndex != SIZE_MAX) {
			m_enemySlotList[enemyIndex].enemy = ObjGUID_NULL;
		}
		m_enemySlotList[sltIndex].enemy = enemy->GetGuid();
	}
}

void Unit::MoveToPriorPosition()
{
	if (m_priorGuid != ObjGUID_NULL) {
		if (m_priorGuid == GetGuid()) {
			MoveToCombatPosition(m_dozeDist, m_dullDist);
		} else {
			auto pTarget = m_pMapInstance->GetUnit(m_priorGuid);
			if (pTarget != NULL) {
				MoveToTarget(pTarget, m_dozeDist, m_dullDist, m_keepDist);
				SetPriorMove(pTarget);
			} else {
				ForceMotionless();
			}
		}
	}
}

void Unit::MoveToCombatPosition(float dozeDist, float dullDist)
{
	auto pTarget = GetTarget();
	if (pTarget != NULL && pTarget->IsInWorld()) {
		auto distSq = GetDistanceSq(pTarget);
		auto bestDist = GetCombatBestDist(pTarget);
		if (distSq > SQ(bestDist + UNIT_AROUND_ENEMY_START_DIST)) {
			MoveToTarget(pTarget, dozeDist, dullDist, bestDist);
		} else {
			auto tgtPos = pTarget->GetPosition();
			auto tgtSlotDir = pTarget->GetEnemySlotDir(this);
			auto tgtSlotPos = tgtPos + tgtSlotDir * bestDist;
			MoveToPosition(tgtSlotPos, dozeDist, dullDist, 0, tgtPos);
		}
		SetMoveMode(MoveByRun);
		SetPriorMove(this);
	}
}

void Unit::MoveToTarget(Unit* pTarget,
	float dozeDist, float dullDist, float keepDist, const vector3f& facePos)
{
	if (pTarget != NULL && pTarget->IsInWorld()) {
		auto tgtPos = pTarget->GetPosition();
		MoveToPosition(tgtPos, dozeDist, dullDist, keepDist, facePos);
	}
}

void Unit::MoveToPosition(const vector3f& tgtPos,
	float dozeDist, float dullDist, float keepDist, const vector3f& facePos)
{
	m_dozeDist = dozeDist, m_dullDist = dullDist;
	if (!IsMoving() && GetDistanceSq(tgtPos) <= SQ(dozeDist)) {
		return;
	}
	if (IsMoving() && m_tgtPos.DistanceSq(tgtPos) <= SQ(dullDist)) {
		StartDullMove(keepDist, facePos);
		return;
	}
	StartMove(keepDist, facePos, tgtPos);
}

void Unit::StartDullMove(float keepDist, const vector3f& facePos)
{
	SetKeepDist(keepDist);
	SetFacePos(facePos);
	m_isPriorMove = false;
}

void Unit::StartMove(float keepDist, const vector3f& facePos, const vector3f& tgtPos)
{
	m_facePos = facePos;
	m_keepDist = keepDist;
	m_isPriorMove = false;
	if (m_isPlanMoving && m_tgtPos == tgtPos) {
		return;
	}

	m_tgtPos = tgtPos;
	m_isPlanMoving = true;

	class PathFinder : public AsyncTask {
	public:
		PathFinder(MapInstance* pMapInstance, ObjGUID ownerGuid,
			const vector3f& startPos, const vector3f& endPos)
			: m_pMapInstance(pMapInstance)
			, m_ownerGuid(ownerGuid)
			, m_startPos(startPos)
			, m_endPos(endPos)
			, m_iMovePathNum(0)
		{}
		virtual void Finish(AsyncTaskOwner *owner) {
			auto pOwner = m_pMapInstance->GetUnit(m_ownerGuid);
			if (pOwner != NULL && pOwner->m_tgtPos == m_endPos) {
				pOwner->SetMovePath(m_fMovePath, m_iMovePathNum);
			}
		}
		virtual void ExecuteInAsync() {
			m_iMovePathNum = m_pMapInstance->
				getGameMap().GetScene()->FindStraightPath(
				m_startPos, m_endPos, m_fMovePath, ARRAY_SIZE(m_fMovePath),
				m_pMapInstance->GetNavPhysicsFlags());
		}
	private:
		MapInstance* const m_pMapInstance;
		const ObjGUID m_ownerGuid;
		const vector3f m_startPos;
		const vector3f m_endPos;
		vector3f m_fMovePath[NAV_PATH_MAX_POLYS];
		int m_iMovePathNum;
	};

	StopMove(), StopTurn();
	sAsyncTaskMgr.AddTask(new PathFinder(m_pMapInstance, GetGuid(),
		GetPosition(), tgtPos), m_pMapInstance, GetGuidLow());
}

void Unit::SetMovePath(const vector3f fMovePath[], int iMovePathNum)
{
	m_isPlanMoving = false;
	if (iMovePathNum <= 0) {
		return;
	}

	m_fMovePath.resize(iMovePathNum);
	std::copy(fMovePath, fMovePath + iMovePathNum, m_fMovePath.begin());
	m_iMovePathCount = 0;
	m_isMoving = true;

	if (IsFacePos()) {
		StartTurn(m_facePos - GetPosition());
	}

	NetPacket pack(SMSG_UNIT_START_MOVE);
	pack << GetGuid() << GetMoveMode() << GetMoveSpeed();
	BuildCreateUpdateMoveBlock(pack);
	SendMessageToSet(pack, true);
}

void Unit::BuildCreateUpdateMoveBlock(INetPacket& pck) const
{
	pck << m_facePos << m_tgtPos << m_keepDist << m_iMovePathCount;
	pck << (u16)SubLeastZero((u32)m_fMovePath.size(), m_iMovePathCount);
	for (size_t i = m_iMovePathCount, n = m_fMovePath.size(); i < n; ++i) {
		pck << m_fMovePath[i];
	}
}

int64 Unit::UpdateMove(int64 availTime)
{
	if (!IsMoving() || IsTurning()) {
		return availTime;
	}

	auto IsKeepDist = [=]() {
		return m_keepDist > .0f &&
			m_fMovePath.size() == m_iMovePathCount + 1;
	};
	auto Turn2Dir = [=](const vector3f& tgtDir) {
		StartTurn(tgtDir);
		return UpdateTurn(availTime);
	};

	const auto isFacePos = IsFacePos();
	const auto speed = GetMoveSpeed();
	const auto &pos = GetPosition(), &dir = GetDirection();
	while (availTime > 0 && m_iMovePathCount < m_fMovePath.size()) {
		const auto &tgtPos = m_fMovePath[m_iMovePathCount];
		const auto tgtVector = tgtPos - pos;
		if (tgtVector.IsZero()) {
			m_iMovePathCount += 1;
			continue;
		}
		if (!isFacePos && (availTime = Turn2Dir(tgtVector)) <= 0) {
			break;
		}
		const auto isKeepDist = IsKeepDist();
		const auto tgtDist = tgtVector.Length();
		const auto moveDist = speed * availTime / 1000;
		const auto moveDir = !isFacePos ? dir : vector3f_normalize(tgtVector);
		if (moveDist < tgtDist - (isKeepDist ? m_keepDist : .0f)) {
			SetPosition(pos + moveDir * moveDist);
			availTime = 0;
		} else {
			if (isKeepDist) {
				if (tgtDist > m_keepDist) {
					SetPosition(pos + moveDir * (tgtDist - m_keepDist));
					availTime -= s64((tgtDist - m_keepDist) / speed * 1000);
				}
			} else {
				SetPosition(tgtPos);
				availTime -= s64(tgtDist / speed * 1000);
			}
			m_iMovePathCount += 1;
		}
	}
	if (isFacePos) {
		SetDirection(m_facePos - pos);
	}

	if (m_iMovePathCount < m_fMovePath.size()) {
		NetPacket pack(SMSG_UNIT_SYNC_MOVE);
		pack << GetGuid() << GetPosition() << GetDirection()
			<< GetMoveMode() << speed << m_iMovePathCount;
		SendMessageToSet(pack, true);
	} else {
		StopMove();
	}

	return availTime;
}

void Unit::CancelPlanMove()
{
	if (m_isPlanMoving) {
		m_tgtPos = vector3f_INVALID;
	}
}

void Unit::StopMove()
{
	if (m_isMoving) {
		m_isMoving = false;
	} else {
		return;
	}

	NetPacket pack(SMSG_UNIT_STOP_MOVE);
	pack << GetGuid() << GetPosition() << GetDirection();
	SendMessageToSet(pack, true);
}

void Unit::SetPriorMove(Unit* pTarget)
{
	m_priorGuid = pTarget != NULL ? pTarget->GetGuid() : ObjGUID_NULL;
	m_isPriorMove = true;
}

void Unit::SetKeepDist(float keepDist)
{
	if (m_keepDist == keepDist) {
		return;
	}

	m_keepDist = keepDist;

	if (IsMoving()) {
		NetPacket pack(SMSG_UNIT_CHANGE_MOVE);
		pack << GetGuid() << MoveChangeKeepDist << keepDist;
		SendMessageToSet(pack, true);
	}
}

void Unit::SetFacePos(const vector3f& facePos)
{
	if (m_facePos == facePos) {
		return;
	}

	m_facePos = facePos;

	if (IsMoving()) {
		NetPacket pack(SMSG_UNIT_CHANGE_MOVE);
		pack << GetGuid() << MoveChangeFacePos << facePos;
		SendMessageToSet(pack, true);
	}
}

void Unit::StartTurn(const vector3f& tgtDir)
{
	if (tgtDir.IsZero()) {
		StopTurn();
		return;
	}

	if (IsTurning()) {
		if (crossProduct(tgtDir, m_tgtDir).LengthSq() == 0) {
			return;
		}
	} else {
		if (crossProduct(tgtDir, GetDirection()).LengthSq() == 0) {
			return;
		}
	}

	(m_tgtDir = tgtDir).normalize();
	m_isTurning = true;

	NetPacket pack(SMSG_UNIT_START_TURN);
	pack << GetGuid() << GetTurnSpeed();
	BuildCreateUpdateTurnBlock(pack);
	SendMessageToSet(pack, true);
}

void Unit::BuildCreateUpdateTurnBlock(INetPacket& pck) const
{
	pck << m_tgtDir;
}

int64 Unit::UpdateTurn(int64 availTime)
{
	if (!IsTurning()) {
		return availTime;
	}

	if (m_pProto->charFlags.isFastTurn) {
		SetDirection(m_tgtDir), StopTurn();
		return availTime;
	}

	if (!m_tgtDir.Has2DAngle() || !GetDirection().Has2DAngle()) {
		SetDirection(m_tgtDir), StopTurn();
		return availTime;
	}

	const auto speed = DEGTORAD(GetTurnSpeed());
	auto includedAngle = Calc2DIncludedAngle(m_tgtDir, GetDirection());
	auto costTime = int64(std::abs(includedAngle) / speed * 1000);
	if (costTime <= availTime) {
		SetDirection(m_tgtDir), StopTurn();
		return availTime - costTime;
	}

	auto newDir = GetDirection();
	auto turnDir = includedAngle < .0f ? -1 : 1;
	newDir.rotateXZBy(availTime * speed * turnDir / 1000);
	SetDirection(newDir);

	NetPacket pack(SMSG_UNIT_SYNC_TURN);
	pack << GetGuid() << GetPosition() << GetDirection() << speed;
	SendMessageToSet(pack, true);

	return 0;
}

void Unit::StopTurn()
{
	if (m_isTurning) {
		m_isTurning = false;
	} else {
		return;
	}

	NetPacket pack(SMSG_UNIT_STOP_TURN);
	pack << GetGuid() << GetPosition() << GetDirection();
	SendMessageToSet(pack, true);
}

bool Unit::LearnSpell(uint32 spellID, uint32 spellLevel, uint32 flags)
{
	if (spellLevel == 0) {
		return false;
	}

	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	if (pSpellProto == NULL || pSpellProto->sliList.empty() ||
		pSpellProto->sliList.size() < spellLevel) {
		return false;
	}

	auto itr = m_spellPropList.find(spellID);
	if (itr == m_spellPropList.end()) {
		itr = m_spellPropList.emplace(spellID, SpellPropInfo{}).first;
	}

	auto& spellInfo = itr->second;
	if (spellInfo.spellLevel >= spellLevel) {
		return false;
	}

	if (pSpellProto->siInfo->spellFlags.isPassive) {
		if (spellInfo.spellLevel != 0) {
			PopPassiveSpell(spellID, spellInfo.spellLevel);
		}
		PushPassiveSpell(spellID, spellLevel);
	}

	spellInfo.pSpellProto = pSpellProto;
	spellInfo.spellLevel = spellLevel;
	spellInfo.flags = flags;

	return true;
}

bool Unit::ForgetSpell(uint32 spellID)
{
	auto itr = m_spellPropList.find(spellID);
	if (itr == m_spellPropList.end()) {
		return false;
	}

	auto& spellInfo = itr->second;
	if (spellInfo.pSpellProto->siInfo->spellFlags.isPassive) {
		PopPassiveSpell(spellID, spellInfo.spellLevel);
	}

	m_spellPropList.erase(itr);
	return true;
}

bool Unit::IsSpellLearned(uint32 spellID) const
{
	return m_spellPropList.count(spellID) != 0;
}

uint32 Unit::GetSpellLevel(uint32 spellID) const
{
	auto itr = m_spellPropList.find(spellID);
	return itr != m_spellPropList.end() ? itr->second.spellLevel : 0;
}

void Unit::PushPassiveSpell(uint32 spellID, uint32 spellLevel)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	if (pSpellProto == NULL || pSpellProto->sliList.empty() ||
		!pSpellProto->siInfo->spellFlags.isPassive ||
		pSpellProto->sliList.size() < spellLevel) {
		WLOG("Push passive spell[%u,%u] invalid.", spellID, spellLevel);
		return;
	}
	auto siInfo = pSpellProto->siInfo;
	auto& passiveSpellInfo = m_allPassiveSpellInfos
		[siInfo->spellPassiveMode][siInfo->spellPassiveBy][spellID];
	passiveSpellInfo.pSpellProto = pSpellProto;
	passiveSpellInfo.spellLevels[spellLevel] += 1;
}

void Unit::PopPassiveSpell(uint32 spellID, uint32 spellLevel)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	if (pSpellProto == NULL ||
		!pSpellProto->siInfo->spellFlags.isPassive) {
		WLOG("Pop passive spell[%u,%u] invalid.", spellID, spellLevel);
		return;
	}
	auto siInfo = pSpellProto->siInfo;
	auto& passiveSpellInfos = m_allPassiveSpellInfos
		[siInfo->spellPassiveMode][siInfo->spellPassiveBy];
	auto itrSpell = passiveSpellInfos.find(spellID);
	if (itrSpell == passiveSpellInfos.end()) {
		return;
	}
	auto& spellLevels = itrSpell->second.spellLevels;
	auto itrLevel = spellLevels.find(spellLevel);
	if (itrLevel == spellLevels.end()) {
		return;
	}
	auto& pair = *itrLevel;
	if (--pair.second != 0) {
		return;
	}
	spellLevels.erase(itrLevel);
	if (!spellLevels.empty()) {
		return;
	}
	passiveSpellInfos.erase(itrSpell);
}

class Unit::PassiveSpellStatusHelper {
public:
	PassiveSpellStatusHelper(Unit* pUnit, SpellPassiveBy spellPassiveBy)
		: m_pUnit(pUnit), m_spellPassiveBy(spellPassiveBy) {
		pUnit->ResetClusterPassiveSpellStatus(spellPassiveBy);
	}
	~PassiveSpellStatusHelper() {
		m_pUnit->ApplyClusterPassiveSpellStatus(m_spellPassiveBy);
	}
private:
	Unit* const m_pUnit;
	SpellPassiveBy m_spellPassiveBy;
};

void Unit::UpdatePassiveSpellStatusByNone()
{
	PassiveSpellStatusHelper _(this, SpellPassiveBy::StatusNone);
	for (auto&[key, info] : m_allPassiveSpellInfos
		[(int)SpellPassiveMode::Status][(int)SpellPassiveBy::StatusNone])
	{
		CachePassiveSpellStatus(
			info.pSpellProto, info.spellLevels.rbegin()->first);
	}
}

void Unit::TriggerPassiveSpellEventByHit()
{
	for (auto&[key, info] : m_allPassiveSpellInfos
		[(int)SpellPassiveMode::Event][(int)SpellPassiveBy::EventHit])
	{
		ArousePassiveSpellEvent(
			info.pSpellProto, info.spellLevels.rbegin()->first);
	}
}

void Unit::TriggerPassiveSpellEventByHitBy()
{
	for (auto&[key, info] : m_allPassiveSpellInfos
		[(int)SpellPassiveMode::Event][(int)SpellPassiveBy::EventHitBy])
	{
		ArousePassiveSpellEvent(
			info.pSpellProto, info.spellLevels.rbegin()->first);
	}
}

void Unit::ArousePassiveSpellEvent(const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	CastWithoutLearnSpell(pSpellProto->siInfo->spellID, spellLevel);
}

void Unit::CachePassiveSpellStatus(const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	auto siInfo = pSpellProto->siInfo;
	auto& statusList = m_passiveSpellStatus[siInfo->spellPassiveBy];
	auto itr = std::find_if(statusList.begin(), statusList.end(),
		[=](const PassiveSpellStatus& status) {
		return status.pSpellProto->siInfo->spellID == siInfo->spellID &&
			status.spellLevel == spellLevel;
	});
	if (itr != statusList.end()) {
		itr->isEnable = true;
	} else {
		statusList.push_back({pSpellProto, spellLevel, true, true});
	}
}

void Unit::ResetClusterPassiveSpellStatus(SpellPassiveBy spellPassiveBy)
{
	auto& statusList = m_passiveSpellStatus[(int)spellPassiveBy];
	for (auto& status : statusList) {
		status.isNew = status.isEnable = false;
	}
}

void Unit::ApplyClusterPassiveSpellStatus(SpellPassiveBy spellPassiveBy)
{
	auto& statusList = m_passiveSpellStatus[(int)spellPassiveBy];
	for (auto itr = statusList.begin(); itr != statusList.end();) {
		auto& status = *itr;
		if (status.isNew) {
			CastWithoutLearnSpell(
				status.pSpellProto->siInfo->spellID, status.spellLevel);
		} else if (!status.isEnable) {
			InterruptSpellById(status.pSpellProto->siInfo->spellID);
			InterruptAllCastAuraObjects(status.pSpellProto->siInfo->spellID);
			itr = statusList.erase(itr);
		} else {
			++itr;
		}
	}
}

GErrorCode Unit::CanGrabFgSpell(
	const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	if (IsOutOfControl() &&
		!pSpellProto->siInfo->spellFlags.isIgnoreOutOfControl) {
		return ErrSpellOutOfControl;
	}
	if (IsSpellCasting()) {
		if (GetSpellCasting()->GetSpellStage() < SpellStageType::Cleanup) {
			return ErrSpellCasting;
		}
	}
	return CommonSuccess;
}

GErrorCode Unit::CanCastWithoutLearnSpell2Target(
	LocatableObject* pTarget, uint32 spellID, uint32 spellLevel,
	int spellCastFlags, const vector3f& tgtEffPos,
	const std::string_view& args)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	return Spell::CanCastSpell(this, pTarget,
		spellCastFlags, pSpellProto, spellLevel, tgtEffPos, args);
}

GErrorCode Unit::CanCastWithoutLearnSpell(
	uint32 spellID, uint32 spellLevel, int spellCastFlags,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	return CanCastWithoutLearnSpell2Target(m_pTarget,
		spellID, spellLevel, spellCastFlags, tgtEffPos, args);
}

GErrorCode Unit::CastWithoutLearnSpell2Target(
	LocatableObject* pTarget, uint32 spellID, uint32 spellLevel,
	uint64 spellInstGuid, const vector3f& tgtEffPos,
	const std::string_view& args)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	return CastSpell(pTarget,
		pSpellProto, spellLevel, spellInstGuid, tgtEffPos, args);
}

GErrorCode Unit::CastWithoutLearnSpell(
	uint32 spellID, uint32 spellLevel, uint64 spellInstGuid,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	return CastWithoutLearnSpell2Target(m_pTarget,
		spellID, spellLevel, spellInstGuid, tgtEffPos, args);
}

GErrorCode Unit::CanCastSpell2Target(
	LocatableObject* pTarget, uint32 spellID, int spellCastFlags,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	auto itr = m_spellPropList.find(spellID);
	if (itr == m_spellPropList.end()) {
		return ErrSpellNotLearned;
	}
	auto spellLevel = itr->second.spellLevel;
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	return Spell::CanCastSpell(this, pTarget,
		spellCastFlags, pSpellProto, spellLevel, tgtEffPos, args);
}

GErrorCode Unit::CanCastSpell(uint32 spellID, int spellCastFlags,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	auto itr = m_spellPropList.find(spellID);
	if (itr == m_spellPropList.end()) {
		return ErrSpellNotLearned;
	}
	auto spellLevel = itr->second.spellLevel;
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	return Spell::CanCastSpell(this, m_pTarget,
		spellCastFlags, pSpellProto, spellLevel, tgtEffPos, args);
}

GErrorCode Unit::CastSpell2Target(
	LocatableObject* pTarget, uint32 spellID, uint64 spellInstGuid,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	auto itr = m_spellPropList.find(spellID);
	if (itr == m_spellPropList.end()) {
		return ErrSpellNotLearned;
	}
	const auto& spellInfo = itr->second;
	return CastSpell(pTarget, spellInfo.pSpellProto,
		spellInfo.spellLevel, spellInstGuid, tgtEffPos, args);
}

GErrorCode Unit::CastSpell(uint32 spellID, uint64 spellInstGuid,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	return CastSpell2Target(
		m_pTarget, spellID, spellInstGuid, tgtEffPos, args);
}

GErrorCode Unit::CastSpell(
	LocatableObject* pTarget, const SpellPrototype* pSpellProto,
	uint32 spellLevel, uint64 spellInstGuid,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	auto errCode = Spell::CanCastSpell(
		this, pTarget, 0, pSpellProto, spellLevel, tgtEffPos, args);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	OnCastSpell(pSpellProto, spellLevel);
	Spell::CastSpell(this, pTarget,
		spellInstGuid, pSpellProto, spellLevel, tgtEffPos, args);
	return CommonSuccess;
}

void Unit::InterruptFgSpell()
{
	if (m_fgSpell != NULL) {
		m_fgSpell->Interrupt(SpellInterruptBy::None);
	}
}

void Unit::InterruptSpell(SpellInterruptBy interruptType, bool isAll)
{
	auto funcInterruptSpell = [=](Spell* pSpell) {
		if (BIT_ISSET(pSpell->GetSpellProto()->siInfo->spellInterruptBys, (int)interruptType)) {
			pSpell->Interrupt(interruptType);
		}
	};
	auto funcInterruptSpellBuff = [=](uint32 key, const SpellBuffInfo* pSpellBuffInfo) {
		if (BIT_ISSET(pSpellBuffInfo->interrupts, (int)interruptType)) {
			InterruptSpellBuffInfo(key);
		}
	};

	if (m_fgSpell != NULL) {
		funcInterruptSpell(m_fgSpell);
	}
	for (ssize_t i = m_bgSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_bgSpells.size(), i) - 1) {
		funcInterruptSpell(m_bgSpells[i]);
	}
	for (ssize_t i = m_stopSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_stopSpells.size(), i) - 1) {
		funcInterruptSpell(m_stopSpells[i]);
	}
	if (isAll) {
		for (const auto& pair : m_spellBuffInfos) {
			funcInterruptSpellBuff(pair.first, pair.second);
		}
	}
}

void Unit::InterruptSpellById(uint32 spellID, bool isAll)
{
	auto funcInterruptSpell = [=](Spell* pSpell) {
		if (pSpell->GetSpellProto()->siInfo->spellID == spellID) {
			pSpell->Interrupt(SpellInterruptBy::None);
		}
	};
	auto funcInterruptSpellBuff = [=](uint32 key, const SpellBuffInfo* pSpellBuffInfo) {
		if (pSpellBuffInfo->pSpellProto->siInfo->spellID == spellID) {
			InterruptSpellBuffInfo(key);
		}
	};

	if (m_fgSpell != NULL) {
		funcInterruptSpell(m_fgSpell);
	}
	for (ssize_t i = m_bgSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_bgSpells.size(), i) - 1) {
		funcInterruptSpell(m_bgSpells[i]);
	}
	for (ssize_t i = m_stopSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_stopSpells.size(), i) - 1) {
		funcInterruptSpell(m_stopSpells[i]);
	}
	if (isAll) {
		for (const auto& pair : m_spellBuffInfos) {
			funcInterruptSpellBuff(pair.first, pair.second);
		}
	}
}

void Unit::InterruptSpellByGuid(uint64 spellInstGuid, bool isAll)
{
	auto funcInterruptSpell = [=](Spell* pSpell) {
		if (pSpell->GetSpellInstGuid() == spellInstGuid) {
			pSpell->Interrupt(SpellInterruptBy::None);
		}
	};
	auto funcInterruptSpellBuff = [=](uint32 key, const SpellBuffInfo* pSpellBuffInfo) {
		if (pSpellBuffInfo->spellInstGuid == spellInstGuid) {
			InterruptSpellBuffInfo(key);
		}
	};

	if (m_fgSpell != NULL) {
		funcInterruptSpell(m_fgSpell);
	}
	for (ssize_t i = m_bgSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_bgSpells.size(), i) - 1) {
		funcInterruptSpell(m_bgSpells[i]);
	}
	for (ssize_t i = m_stopSpells.size() - 1; i >= 0; i = std::min<ssize_t>(m_stopSpells.size(), i) - 1) {
		funcInterruptSpell(m_stopSpells[i]);
	}
	if (isAll) {
		for (const auto& pair : m_spellBuffInfos) {
			funcInterruptSpellBuff(pair.first, pair.second);
		}
	}
}

void Unit::InterruptSpellBuffByType(uint32 spellEffectType)
{
	auto funcInterruptSpellBuff = [=](uint32 key, const SpellBuffInfo* pSpellBuffInfo) {
		auto sleiInfo = Spell::GetSpellLevelProto(
			pSpellBuffInfo->pSpellProto, pSpellBuffInfo->spellLevel)->
			sleiList[pSpellBuffInfo->effectIndex].sleiInfo;
		if (sleiInfo->spellEffectType == spellEffectType) {
			InterruptSpellBuffInfo(key);
		}
	};
	for (const auto& pair : m_spellBuffInfos) {
		funcInterruptSpellBuff(pair.first, pair.second);
	}
}

void Unit::InterruptSpellBuffByStyle(uint32 spellEffectStyle)
{
	auto funcInterruptSpellBuff = [=](uint32 key, const SpellBuffInfo* pSpellBuffInfo) {
		auto sleiInfo = Spell::GetSpellLevelProto(
			pSpellBuffInfo->pSpellProto, pSpellBuffInfo->spellLevel)->
			sleiList[pSpellBuffInfo->effectIndex].sleiInfo;
		if (sleiInfo->spellEffectStyle == spellEffectStyle) {
			InterruptSpellBuffInfo(key);
		}
	};
	for (const auto& pair : m_spellBuffInfos) {
		funcInterruptSpellBuff(pair.first, pair.second);
	}
}

void Unit::OnCastSpell(const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	if (pSpellProto->siInfo->spellFlags.isExclusive) {
		if (IsSpellCasting()) {
			InterruptFgSpell();
		}
		ForceMotionless();
	}
}

void Unit::OnStartSpell(Spell* pSpell)
{
	if (pSpell->IsExclusive()) {
		DBGASSERT(m_fgSpell == NULL);
		m_fgSpell = pSpell;
	} else {
		m_bgSpells.push_back(pSpell);
	}
}

void Unit::OnStopSpell(Spell* pSpell)
{
	if (pSpell->IsExclusive()) {
		DBGASSERT(m_fgSpell == pSpell);
		m_fgSpell = NULL;
	} else {
		m_bgSpells.erase(std::find(
			m_bgSpells.begin(), m_bgSpells.end(), pSpell));
	}
	if (pSpell->IsDeletable()) {
		m_zombieSpells.push_back(pSpell);
	} else {
		m_stopSpells.push_back(pSpell);
	}
}

void Unit::OnReleaseSpell(Spell* pSpell)
{
	m_zombieSpells.push_back(pSpell);
	m_stopSpells.erase(std::find(
		m_stopSpells.begin(), m_stopSpells.end(), pSpell));
}

void Unit::DestructAllSpells()
{
	if (m_fgSpell != NULL) {
		Spell::Delete(m_fgSpell);
	}
	for (auto pSpell : m_bgSpells) {
		Spell::Delete(pSpell);
	}
	for (auto pSpell : m_stopSpells) {
		Spell::Delete(pSpell);
	}
	for (auto pSpell : m_zombieSpells) {
		Spell::Delete(pSpell);
	}
}

void Unit::CleanSpells()
{
	for (auto pSpell : m_zombieSpells) {
		Spell::Delete(pSpell);
	}
	m_zombieSpells.clear();
}

void Unit::SpellBuffEvent_OnHurt(Unit* pVictim, uint64 hurtValue)
{
	ForeachHookInfo4Event(m_spellBuffInfos, SpellBuffEvent::OnHurt, "OnHurt", pVictim, hurtValue);
}

void Unit::SpellBuffEvent_OnHurted(Unit* pHurter, uint64 hurtValue)
{
	ForeachHookInfo4Event(m_spellBuffInfos, SpellBuffEvent::OnHurted, "OnHurted", pHurter, hurtValue);
}

void Unit::SpellBuffEvent_OnKill(Unit* pVictim)
{
	ForeachHookInfo4Event(m_spellBuffInfos, SpellBuffEvent::OnKill, "OnKill", pVictim);
}

void Unit::SpellBuffEvent_OnKilled(Unit* pKiller)
{
	ForeachHookInfo4Event(m_spellBuffInfos, SpellBuffEvent::OnKilled, "OnKilled", pKiller);
}

void Unit::AttachSpellBuffInfo(LuaTable&& t)
{
	auto key = NewSpellBuffKey();
	auto pSpellBuffInfo = new SpellBuffInfo{true};
	HookEvents2bitset(t.get<LuaTable>("events"), pSpellBuffInfo->events);
	pSpellBuffInfo->isMember = t.get<bool>("isMember?");
	pSpellBuffInfo->spellInstGuid = t.get<uint64>("guid");
	pSpellBuffInfo->pSpellProto = t.get<const SpellPrototype*>("proto");
	pSpellBuffInfo->spellLevel = t.get<uint32>("level");
	pSpellBuffInfo->effectIndex = t.get<uint32>("index");
	pSpellBuffInfo->interrupts = t.get<uint32>("interrupts");
	pSpellBuffInfo->duration = t.get<uint64>("duration");
	pSpellBuffInfo->start = t.get<int64>("start");
	pSpellBuffInfo->t = LuaRef(t.getL(), t.index());
	m_spellBuffInfos.emplace(key, pSpellBuffInfo);
	SendSpellBuffInfo(key, pSpellBuffInfo, true);
	LuaFuncs(t).CallMethod<void>("OnAttach", key);
}

void Unit::DetachSpellBuffInfo(uint32 key)
{
	auto itr = m_spellBuffInfos.find(key);
	if (itr == m_spellBuffInfos.end()) {
		WLOG("DetachSpellBuffInfo: Can't find key %d.", key);
		return;
	}
	auto pSpellBuffInfo = itr->second;
	if (!pSpellBuffInfo->isAvail) {
		WLOG("DetachSpellBuffInfo: key %d isn't available.", key);
		return;
	}
	pSpellBuffInfo->isAvail = false;
	m_gcSpellBuffKeys.push_back(key);
	SendSpellBuffInfo(key, pSpellBuffInfo, false);
	LuaFuncs(pSpellBuffInfo->t).CallMethod<void>("OnDetach");
}

void Unit::InterruptSpellBuffInfo(uint32 key)
{
	DetachSpellBuffInfo(key);
}

void Unit::SendSpellBuffInfo(
	uint32 key, const SpellBuffInfo* pSpellBuffInfo, bool isAttach)
{
	auto sleiInfo = Spell::GetSpellLevelProto(
		pSpellBuffInfo->pSpellProto, pSpellBuffInfo->spellLevel)->
		sleiList[pSpellBuffInfo->effectIndex].sleiInfo;
	if (!sleiInfo->spellEffectFlags.isSync2Client ||
		!sleiInfo->spellEffectFlags.isSync2AllClient) {
		return;
	}
	NetPacket pack(SMSG_SPELL_SYNC_BUFF_INFO);
	pack << GetGuid() << isAttach << key;
	if (isAttach) {
		pack << pSpellBuffInfo->spellInstGuid
			<< pSpellBuffInfo->pSpellProto->siInfo->spellID
			<< pSpellBuffInfo->spellLevel << pSpellBuffInfo->effectIndex
			<< pSpellBuffInfo->duration << SubLeastZero(
				pSpellBuffInfo->start + pSpellBuffInfo->duration, GET_SYS_TIME);
	}
	PushMessage(pack, sleiInfo->spellEffectFlags.isSync2AllClient);
}

void Unit::DestructAllSpellBuffInfos()
{
	for (auto& pair : m_spellBuffInfos) {
		delete pair.second;
	}
}

void Unit::CleanSpellBuffInfos()
{
	for (; !m_gcSpellBuffKeys.empty(); m_gcSpellBuffKeys.pop_back()) {
		auto key = m_gcSpellBuffKeys.back();
		auto itr = m_spellBuffInfos.find(key);
		if (itr == m_spellBuffInfos.end()) {
			WLOG("CleanSpellBuffInfo: Can't find key %d.", key);
			continue;
		}
		auto pSpellBuffInfo = itr->second;
		if (pSpellBuffInfo->isAvail) {
			WLOG("CleanSpellBuffInfo: key %d is available.", key);
			continue;
		}
		m_spellBuffInfos.erase(itr);
		delete pSpellBuffInfo;
	}
}

GErrorCode Unit::Cooldown_CanCast(const SpellPrototype* pSpellProto) const
{
	auto errCode = _Cooldown_CanCast_Raw(
		COOLDOWN_TYPE_SPELL, pSpellProto->siInfo->spellID);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	return CommonSuccess;
}

GErrorCode Unit::Cooldown_CanCast_Item(const ItemPrototype *pItemProto) const
{
	if (pItemProto->itemSpellId != 0) {
		auto errCode = _Cooldown_CanCast_Raw(
			COOLDOWN_TYPE_SPELL, pItemProto->itemScriptId);
		if (errCode != CommonSuccess) {
			return errCode;
		}
	}
	return CommonSuccess;
}

GErrorCode Unit::_Cooldown_CanCast_Raw(CooldownType Type, uint32 Key) const
{
	const auto& cooldownMap = m_cooldownMap[Type];
	auto itr = cooldownMap.find(Key);
	if (itr != cooldownMap.end()) {
		const auto& cooldownInfo = itr->second;
		if (cooldownInfo.ExpireTime > GET_SYS_TIME) {
			return ErrSpellCooldown;
		}
	}
	return CommonSuccess;
}

void Unit::Cooldown_Add(const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	auto pSpellLevelProto = Spell::GetSpellLevelProto(pSpellProto, spellLevel);
	auto spellCDTime = pSpellLevelProto->sliInfo->spellCDTime;
	_Cooldown_Add_Raw(pSpellProto, COOLDOWN_TYPE_SPELL,
		pSpellProto->siInfo->spellID, spellCDTime, spellCDTime);
}

void Unit::Cooldown_Add_Item(const ItemPrototype *pItemProto)
{
	if (pItemProto->itemSpellId != 0) {
		auto pSpellProto = sSpellMgr.GetSpellPrototype(pItemProto->itemSpellId);
		if (pSpellProto != NULL && !pSpellProto->sliList.empty() &&
			pSpellProto->sliList.size() >= pItemProto->itemSpellLevel)
		{
			auto pSpellLevelProto =
				Spell::GetSpellLevelProto(pSpellProto, pItemProto->itemSpellLevel);
			auto spellCDTime = pSpellLevelProto->sliInfo->spellCDTime;
			_Cooldown_Add_Raw(pSpellProto, COOLDOWN_TYPE_SPELL,
				pSpellProto->siInfo->spellID, spellCDTime, spellCDTime);
		}
	}
}

void Unit::Cooldown_Reset(const SpellPrototype* pSpellProto)
{
	_Cooldown_Remove(COOLDOWN_TYPE_SPELL, pSpellProto->siInfo->spellID);
}

void Unit::Cooldown_Reset_Item(const ItemPrototype *pItemProto)
{
	if (pItemProto->itemSpellId != 0) {
		_Cooldown_Remove(COOLDOWN_TYPE_SPELL, pItemProto->itemSpellId);
	}
}

void Unit::_Cooldown_Add_Raw(const SpellPrototype* pSpellProto,
	CooldownType Type, uint32 Key, uint64 CooldownRemain, uint64 CooldownMax)
{
	if (Type < 0 || Type >= COOLDOWN_TYPE_COUNT) {
		return;
	}
	if (CooldownMax == 0) {
		return;
	}

	if (CooldownRemain > CooldownMax) {
		CooldownRemain = CooldownMax;
	}

	auto& cooldownMap = m_cooldownMap[Type];
	auto itr = cooldownMap.find(Key);
	if (itr == cooldownMap.end()) {
		itr = cooldownMap.emplace(Key, SpellCooldownInfo{0,0}).first;
	}

	auto& cooldownInfo = itr->second;
	if (cooldownInfo.ExpireTime > GET_SYS_TIME + CooldownRemain) {
		return;
	}

	cooldownInfo.pSpellProto = pSpellProto;
	cooldownInfo.ExpireTime = GET_SYS_TIME + CooldownRemain;
	cooldownInfo.RecoveryTime = CooldownMax;

	NetPacket pack(SMSG_SPELL_COOLDOWN);
	pack << Type << Key << CooldownRemain << CooldownMax;
	SendSyncCooldownPacket(pSpellProto, pack);
}

void Unit::_Cooldown_Remove(CooldownType Type, uint32 Key)
{
	if (Type < 0 || Type >= COOLDOWN_TYPE_COUNT) {
		return;
	}

	auto& cooldownMap = m_cooldownMap[Type];
	auto itr = cooldownMap.find(Key);
	if (itr == cooldownMap.end()) {
		return;
	}

	auto& cooldownInfo = itr->second;
	auto pSpellProto = cooldownInfo.pSpellProto;
	cooldownMap.erase(itr);

	NetPacket pack(SMSG_SPELL_REMOVE_COOLDOWN);
	pack << Type << Key;
	SendSyncCooldownPacket(pSpellProto, pack);
}

void Unit::SendSyncCooldownPacket(
	const SpellPrototype* pSpellProto, const INetPacket& pck)
{
	PushMessage(pck, pSpellProto->siInfo->spellFlags.isAppearance);
}

void Unit::AddReferAuraObject(AuraObject* pAuraObject)
{
	m_referAuraObjects.insert(pAuraObject);
}

void Unit::RemoveReferAuraObject(AuraObject* pAuraObject)
{
	m_referAuraObjects.erase(pAuraObject);
}

void Unit::MoveAllReferAuraObjects()
{
	for (auto pAuraObject : m_referAuraObjects) {
		if (pAuraObject->getProto()->isFollowOwner &&
			pAuraObject->getOwner() == this) {
			m_pMapInstance->MoveAoiActorOrder(
				pAuraObject, m_position.x, m_position.z);
		}
	}
}

void Unit::InterruptAllReferAuraObjects()
{
	for (auto pAuraObject : m_referAuraObjects) {
		pAuraObject->FastDisappear();
	}
}

void Unit::InterruptAllCastAuraObjects(uint32 spellID)
{
	for (auto pAuraObject : m_referAuraObjects) {
		if (pAuraObject->getSpellProto()->siInfo->spellID == spellID &&
			pAuraObject->getCaster() == this) {
			pAuraObject->FastDisappear();
		}
	}
}

void Unit::ObjectHookEvent_OnUnitChangeCombatStatus()
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnUnitChangeCombatStatus, "OnUnitChangeCombatStatus", IsInCombat());
}

void Unit::ObjectHookEvent_OnUnitHurted(Unit* pHurter, uint64 hurtValue)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnUnitHurted, "OnUnitHurted", pHurter, hurtValue);
}

void Unit::ObjectHookEvent_OnUnitKilled(Unit* pKiller)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnUnitKilled, "OnUnitKilled", pKiller);
}

void Unit::ObjectHookEvent_OnUnitDead(Unit* pKiller)
{
	ForeachHookInfo4Event(m_objectHookInfos, ObjectHookEvent::OnUnitDead, "OnUnitDead", pKiller);
}
